#ifndef _3DAPP_H_
#define _3DAPP_H_

#include <vector>
#include <iostream>
#include <cmath>

#define X 0
#define Y 1
#define Z 2
#define W 3

using namespace std;

struct Vector
{
  private:

    float data[4];

  public:
  
    Vector(void)
    {
        data[X] = 
            data[Y] =
            data[Z] = 
            data[W] = 0.f;
    }

    Vector(float x, float y, float z, float w)
    {
        data[X] = x;
        data[Y] = y;
        data[Z] = z;
        data[W] = w;
        
    }

    float &operator [](unsigned int idx)
    {
        return data[idx];
    }  
};

struct Point
{
  private:

    float data[4];

  public:
  
    Point(void)
    {
        data[X] = 
            data[Y] =
            data[Z] = 0.f; 
        data[W] = 1.f;
    }

    Point(float x, float y, float z, float w)
    {
        data[X] = x;
        data[Y] = y;
        data[Z] = z;
        data[W] = w;
    }

    float &operator [](unsigned int idx)
    {
        return data[idx];
    }  

    const float *gldata(void)
    {
        return data;
    }
};

struct Vertex
{
    // Vertex Coordinate
    Point    v;

    // Vertex Normal
    Vector   n;
};

struct Face
{
    // Vertex Index
    int v1,v2,v3;
	// normal vector
	Vector normal;
	// centroid of triangle face
	float c[3];
	// area of faces
	float area;
};

struct BoundingBox
{
    Point  minP;
    Point  maxP;
    Vector vSize;
};

struct Object
{
    std::vector<Vertex> vertices;
    std::vector<Face  > faces;
	std::vector<Vertex> newVertices;
	float center[3];

    BoundingBox         bBox;
};

inline
void subPnt(Vector &vResult, Point &p1, Point &p2)
{
    vResult[X] = p1[X] - p2[X];
    vResult[Y] = p1[Y] - p2[Y];
    vResult[Z] = p1[Z] - p2[Z];
    vResult[W] = p1[W] - p2[W];
}

inline
void add(Vector &vResult, Vector &v1, Vector &v2)
{
    vResult[X] = v1[X] + v2[X];
    vResult[Y] = v1[Y] + v2[Y];
    vResult[Z] = v1[Z] + v2[Z];
    vResult[W] = v1[W] + v2[W];
}

inline
void addScaled(Point &result, Point &p, Vector &v, float s)
{
    result[X] = p[X] + (v[X] * s);
    result[Y] = p[Y] + (v[Y] * s);
    result[Z] = p[Z] + (v[Z] * s);
    result[W] = p[W] + (v[W] * s);
}

inline
void assign(Vector &v1, Vector &v2)
{
    v1[X] = v2[X];
    v1[Y] = v2[Y];
    v1[Z] = v2[Z];
    v1[W] = v2[W];
}

inline
float dot(Vector &v1, Vector &v2)
{
    return v1[X] * v2[X] + v1[Y] * v2[Y] + v1[Z] * v2[Z];
}

inline
void cross(Vector &result,Vector &v1, Vector &v2)
{
	result[X] = v1[Y]*v2[Z]-v1[Z]*v2[Y];
	result[Y] = v1[Z]*v2[X]-v1[X]*v2[Z];
	result[Z] = v1[X]*v2[Y]-v1[Y]*v2[X];
	result[W] = 0;
}

inline
float length(Vector &v)
{
    return sqrtf(v[X] * v[X] + v[Y] * v[Y] + v[Z] * v[Z]);
}

inline
void normalize(Vector &v)
{
    float fLength = length(v);

    fLength = 1.f / fLength;

    v[X] *= fLength;
    v[Y] *= fLength;
    v[Z] *= fLength;
    v[W] *= fLength;
}

inline
float maxComponent3(Vector &v)
{
    float returnValue = v[X];

    if(v[Y] > returnValue)
    {
        returnValue = v[Y];
    }
    
    if(v[Z] > returnValue)
    {
        returnValue = v[Z];
    }

    return returnValue;
}

inline
void dumpPnt(Point &p)
{
    fprintf(stderr, "%f %f %f %f\n",
            p[0],
            p[1],
            p[2],
            p[3]);
}

inline
void dumpPnt(const char *szInfo, Point &p)
{
    fprintf(stderr, "%s\n%f %f %f %f\n",
            szInfo,
            p[0],
            p[1],
            p[2],
            p[3]);
}

inline
float dot3(float *a, float *b){
	float dot=0;
	for(int i=0;i<3;i++)
		dot +=a[i]*b[i];
	return dot;
}

inline
void cross3(float *result, float *a, float *b){
	result[0]=a[1]*b[2]-a[2]*b[1];
	result[1]=a[2]*b[0]-a[0]*b[2];
	result[2]=a[0]*b[1]-a[1]*b[0];
}

inline
void sub3(float *result, float *a, float *b){
	for(int i=0;i<3;i++)
		result[i]=a[i]-b[i];
}

inline
float length3(float *a){
	float l = sqrt(pow(a[0],2)+pow(a[1],2)+pow(a[2],2));
	return l;
}

inline
void normalize3(double *a){
	float l = sqrt(pow(a[0],2)+pow(a[1],2)+pow(a[2],2));
	for(int i=0;i<3;i++)
		a[i]/=l;
}

inline
void addScaled3(float *result, float*a, float*b, float k){
	for(int i=0; i<3; i++)
		result[i]=k*a[i]+b[i];
}

inline
void dumpFloat3(float *a)
{
    cout<<a[0]<<" "<<a[1]<<" "<<a[2]<<endl;
}
#endif